#include "libsnes.hpp"
#include "libsnes-clr.hpp"

#include <stdlib.h>
#include <windows.h>


// ============
// CLR specific
// ============

#define VIDEO_BUFFER_LENGTH (512 * 478)
#define AUDIO_BUFFER_LENGTH 2048

static uint32_t audio_buffer[AUDIO_BUFFER_LENGTH] = { 0 };
static unsigned audio_count = 0;
static volatile uint16_t input_buttons[8] = { 0 };
static volatile int32_t input_coords[8] = { 0 };
static unsigned input_count = 0;
static HANDLE native_event = NULL;
static HANDLE managed_event = NULL;
static clr_snes_frame_complete_t frame_complete_callback = NULL;

void clr_snes_get_buffer_lengths(unsigned *video, unsigned *audio)
{
	if (video)
	{
		*video = VIDEO_BUFFER_LENGTH;
	}

	if (audio)
	{
		*audio = AUDIO_BUFFER_LENGTH;
	}
}

void clr_snes_set_frame_complete(clr_snes_frame_complete_t callback)
{
	frame_complete_callback = callback;
}

HANDLE clr_snes_enable_synced_input(HANDLE managed)
{
	managed_event = managed;

	if (managed_event && !native_event)
	{
		native_event = CreateEvent(NULL, FALSE, FALSE, "clr_snes_native_input_event");
	}
}

void clr_snes_set_input_state(unsigned port, unsigned index, uint16_t buttons, int32_t coords)
{
	int i = port * 4 + index;
	input_buttons[i] = buttons;
	input_coords[i] = coords;
}

static void snes_video_refresh_handler(const uint16_t *data, unsigned width, unsigned height)
{
	if (frame_complete_callback)
	{
		frame_complete_callback(data, width, height, audio_buffer, audio_count);
	}

	audio_count = 0;
	input_count = 0;
}

static void snes_audio_sample_handler(uint16_t left, uint16_t right)
{
	if (audio_count >= AUDIO_BUFFER_LENGTH)
	{
		audio_count = 0;
	}

	audio_buffer[audio_count++] = (right << 16) | left;
}

static void snes_input_poll_handler()
{
	if (input_count++ >= 2) { return; }

	if (managed_event)
	{
		SetEvent(native_event);
		WaitForSingleObject(managed_event, INFINITE);
	}
}

static int16_t snes_input_state_handler(bool port, unsigned device, unsigned index, unsigned id)
{
	int i = (port ? 1 : 0) * 4 + index;

	if (device >= SNES_DEVICE_MOUSE && id <= 1)
	{
		return (int16_t)(input_coords[i] >> (id * 16));
	}
	else
	{
		return (input_buttons[i] & (1 << id)) != 0;
	}
}

// ================
// libsnes wrappers
// ================

unsigned clr_snes_library_revision_major()
{
	return snes_library_revision_major();
}

unsigned clr_snes_library_revision_minor()
{
	return snes_library_revision_minor();
}

void clr_snes_set_controller_port_device(bool port, unsigned device)
{
	snes_set_controller_port_device(port, device);
}

void clr_snes_init()
{
	snes_set_video_refresh(snes_video_refresh_handler);
	snes_set_audio_sample(snes_audio_sample_handler);
	snes_set_input_poll(snes_input_poll_handler);
	snes_set_input_state(snes_input_state_handler);

	snes_init();
}

void clr_snes_term()
{
	snes_term();

	if (native_event)
	{
		CloseHandle(native_event);
	}
}

void clr_snes_power()
{
	snes_power();
}

void clr_snes_reset()
{
	snes_reset();
}

void clr_snes_run()
{
	snes_run();
}

unsigned clr_snes_serialize_size()
{
	return snes_serialize_size();
}

bool clr_snes_serialize(uint8_t *data, unsigned size)
{
	return snes_serialize(data, size);
}

bool clr_snes_unserialize(const uint8_t *data, unsigned size)
{
	return snes_unserialize(data, size);
}

void clr_snes_cheat_reset()
{
	snes_cheat_reset();
}

void clr_snes_cheat_set(unsigned index, bool enabled, const char *code)
{
	snes_cheat_set(index, enabled, code);
}

void clr_snes_load_cartridge_normal(
	const char *rom_xml, const uint8_t *rom_data, unsigned rom_size)
{
	snes_load_cartridge_normal(rom_xml, rom_data, rom_size);
}

void clr_snes_load_cartridge_bsx_slotted(
	const char *rom_xml, const uint8_t *rom_data, unsigned rom_size,
	const char *bsx_xml, const uint8_t *bsx_data, unsigned bsx_size)
{
	snes_load_cartridge_bsx_slotted(rom_xml, rom_data, rom_size, bsx_xml, bsx_data, bsx_size);
}

void clr_snes_load_cartridge_bsx(
	const char *rom_xml, const uint8_t *rom_data, unsigned rom_size,
	const char *bsx_xml, const uint8_t *bsx_data, unsigned bsx_size)
{
	snes_load_cartridge_bsx(rom_xml, rom_data, rom_size, bsx_xml, bsx_data, bsx_size);
}

void clr_snes_load_cartridge_sufami_turbo(
	const char *rom_xml, const uint8_t *rom_data, unsigned rom_size,
	const char *sta_xml, const uint8_t *sta_data, unsigned sta_size,
	const char *stb_xml, const uint8_t *stb_data, unsigned stb_size)
{
	snes_load_cartridge_sufami_turbo(rom_xml, rom_data, rom_size, sta_xml, sta_data, sta_size, stb_xml, stb_data, stb_size);
}

void clr_snes_load_cartridge_super_game_boy(
	const char *rom_xml, const uint8_t *rom_data, unsigned rom_size,
	const char *dmg_xml, const uint8_t *dmg_data, unsigned dmg_size)
{
	snes_load_cartridge_super_game_boy(rom_xml, rom_data, rom_size, dmg_xml, dmg_data, dmg_size);
}

void clr_snes_unload_cartridge()
{
	snes_unload_cartridge();
}

bool clr_snes_get_region()
{
	return snes_get_region();
}

uint8_t* clr_snes_get_memory_data(unsigned id)
{
	return snes_get_memory_data(id);
}

unsigned clr_snes_get_memory_size(unsigned id)
{
	return snes_get_memory_size(id);
}
